#include "sshconsole.h"

#include <QThread>
#include <QTextCursor>
#include <QDebug>
#include <QKeyEvent>
#include <QRegularExpression>
#include <QRegularExpressionMatch>

// TODO:
// 1、使用Qt源代码中的ansi转义字符解析器来弄 √
// 2、将复制、粘贴完善一下 和 contextMenu 完善一下
// 3、完善一下SshCreateGuide类 √

SshConsole::SshConsole(const Ssh::SshLoginKeys& loginKeys, QWidget* parent)
    : QTextEdit(parent)
{
    Init(loginKeys);
    InitUi();
}

SshConsole::~SshConsole() {
    mSshConnection->DisconnectSsh();
    // mSshConnection->deleteLater();
    // mSshThread->deleteLater();
}


void SshConsole::Init(const Ssh::SshLoginKeys& loginKeys) {
    // 快捷键
    mZoomIn = new QAction("ZoomIn", this);
    mZoomOut = new QAction("ZoomIn", this);

    mZoomIn->setShortcuts(QList<QKeySequence>() << QKeySequence::ZoomIn << QKeySequence(Qt::CTRL | Qt::Key_Equal));
    mZoomOut->setShortcut(QKeySequence::ZoomOut);

    connect(mZoomIn, &QAction::triggered, this, [this] {
        this->zoomIn();
    });
    connect(mZoomOut, &QAction::triggered, this, [this] {
        this->zoomOut();
    });

    this->addAction(mZoomIn);
    this->addAction(mZoomOut);

    // ssh会话
    mSshConnection = new Ssh::SshConnection(loginKeys);
    mSshThread = new QThread;

    connect(mSshConnection, &Ssh::SshConnection::SshConnected,
        this, &SshConsole::DoWhenConnected, Qt::QueuedConnection);
    connect(mSshConnection, &Ssh::SshConnection::SshStandardError,
        this, &SshConsole::GetExecStandardError, Qt::QueuedConnection);

    connect(mSshConnection, &Ssh::SshConnection::SshStandardOutput,
        this, &SshConsole::GetExecStandardOutput, Qt::QueuedConnection);

    connect(mSshConnection, &Ssh::SshConnection::SshExecStarted,
            this, [&] { qDebug() << "SshExecStarted"; mIsExecStarted = true; }, Qt::QueuedConnection);

    connect(mSshConnection, &Ssh::SshConnection::SshExecFinished,
        this, &SshConsole::DoWhenExecFinished, Qt::QueuedConnection);
    mSshConnection->moveToThread(mSshThread);
    connect(mSshThread, &QThread::started, mSshConnection, &Ssh::SshConnection::ConnectSsh);
    connect(mSshThread, &QThread::finished, this, [this] {
        qDebug() << "Ssh connection thread finished";
        mSshConnection->deleteLater();
        mSshThread->deleteLater();
    });
    mSshConnection->moveToThread(mSshThread);
    mSshThread->start();
}

void SshConsole::InitUi() {
    this->setFocusPolicy(Qt::StrongFocus);
}

void SshConsole::SetPrompt(const QString &prompt, bool IsShowPrompt) {
    mPrompt = prompt;
    mIsShowPrompt = IsShowPrompt;
}

void SshConsole::SetIsShowPrompt(bool isShowPrompt) {
    mIsShowPrompt = isShowPrompt;
}


bool SshConsole::IsShowPrompt() const {
    return mIsShowPrompt;
}

void SshConsole::ExecCommand(const QString& command) {
    mSshConnection->SendCommand(command.toStdString());
    mHistoryCommands.append(mCurrentCommand);
    mCurrentCommand.clear();
    this->append("");
}

// protected

void SshConsole::mousePressEvent(QMouseEvent *event) {
    // 记录选择起始位置
    if (event->button() != Qt::LeftButton) {
        QTextEdit::mousePressEvent(event);
        return;
    }

    // 设置只读，隐藏光标
    this->setReadOnly(true);

    // 重置掉以前选择区域的背景色
    auto cursor = textCursor();
    mCurrentPosition = cursor.position();
    if (!mSelectedCursor.isNull()) {
        auto format = QTextCharFormat();
        format.setBackground(Qt::transparent);
        mSelectedCursor.mergeCharFormat(format);
        setTextCursor(mSelectedCursor);
        mSelectedCursor = QTextCursor();
        setTextCursor(cursor);
    }

    auto mouseCursor = cursorForPosition(event->pos());
    mSelectionStart = mouseCursor.position();
    // qDebug() << "mouseCursor.position()=" << mouseCursor.position()
    //          << "textCursor().position()=" << textCursor().position()
    //          << "cursor.position()=" << cursor.position();
    cursor.movePosition(QTextCursor::StartOfLine);
    if (mouseCursor.position() - cursor.position() < mPrompt.length() + mRelativePath.length() + 1) {
        return;
    }
    mCurrentPosition = mSelectionStart;

    QTextEdit::mousePressEvent(event);
}

void SshConsole::mouseMoveEvent(QMouseEvent *event) {
    if (!(event->buttons() & Qt::LeftButton)) {
        QTextEdit::mouseMoveEvent(event);
        return;
    }

    // qDebug() << "mouseMoveEvent, cursorWidth=" << this->cursorWidth();

    if (mSelectionStart == -1) {
        qDebug() << "mouseMoveEvent, mSelectionStart == -1";
        return;
    }

    auto currentCursor = textCursor();
    const auto currentCursorPos = this->mapToGlobal(this->cursorRect().topLeft());
    auto mouseCursor = cursorForPosition(event->pos());
    currentCursor.setPosition(mSelectionStart, QTextCursor::MoveAnchor);
    currentCursor.setPosition(mouseCursor.position(), QTextCursor::KeepAnchor);
    setTextCursor(currentCursor);
}

void SshConsole::mouseReleaseEvent(QMouseEvent *event) {
    qDebug() << "mouseReleaseEvent, button=" << event->button();
    if (event->button() != Qt::LeftButton) {
        QTextEdit::mouseMoveEvent(event);
        return;
    }
    // 记录选择区域
    mSelectedCursor = textCursor();
    // 将选择区域背景色改成选择中的颜色
    auto cursor = textCursor();
    auto format = QTextCharFormat();
    format.setBackground(mSelectionColor);
    cursor.mergeCharFormat(format);
    // 重置选择起始位置， 恢复到原来的鼠标位置并显示鼠标
    cursor.setPosition(mCurrentPosition, QTextCursor::MoveAnchor);
    format = QTextCharFormat();
    format.setBackground(Qt::transparent);
    cursor.mergeCharFormat(format);
    setTextCursor(cursor);
    mSelectionStart = -1;
    this->setReadOnly(false);
}


void SshConsole::keyPressEvent(QKeyEvent* event) {
    qDebug() << "SshConsole::keyPressEvent, key=" << event->key();
    const auto key = event->key();

    if (key == Qt::Key_Return) {
        qDebug() << "SshConsole::Key_Return";
        auto cursor = this->textCursor();
        cursor.movePosition(QTextCursor::StartOfLine);
        cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, mPrompt.length() + mRelativePath.length());
        cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
        mCurrentCommand = cursor.selectedText();
        mHistoryCommands.push_front(mCurrentCommand);
        ++mCountOfHistory;
        ExecCommand(mCurrentCommand + "\n");
        event->accept();
        return;
    }

    if (key == Qt::Key_Down || key == Qt::Key_Up) {
        ShowHistoryCommand(key);
        event->accept();
        return;
    }


    if (MoveCursorByKey(key)) {
        event->accept();
        return;
    }

    if (RemoveForwardByKey(key)) {
        event->accept();
        return;
    }

    QTextEdit::keyPressEvent(event);
}

void SshConsole::ShowHistoryCommand(const int key) {
    if (mHistoryCommands.isEmpty()) {
        return;
    }
    if (key == Qt::Key_Up) {
        mIndexOfHistory = mIndexOfHistory + 1 < mCountOfHistory ? mIndexOfHistory + 1 : mIndexOfHistory;
    } else if (key == Qt::Key_Down) {
        mIndexOfHistory = mIndexOfHistory >= 0 ? mIndexOfHistory - 1 : -1;
    }
    // show history command
    if (mIndexOfHistory != -1) {
        ReplaceCurrentCommand(mHistoryCommands[mIndexOfHistory]);
    } else {
        ReplaceCurrentCommand(mCurrentCommand);
    }
}

// private slots

void SshConsole::DoWhenConnected() {
    // welcome..
    this->setTextColor(mPromptColor);
    this->append(mPrompt + mRelativePath);
    // enable to edit
    this->setTextColor(mInfoColor);
    this->insertPlainText(" ");
}

void SshConsole::GetExecStandardOutput(const std::string &output) {
    this->setTextColor(mInfoColor);
    qDebug() << "SshConsole::GetExecStandardOutput:";
    auto text = FilterUnusedChars(QString::fromStdString(output));
    if (text.isEmpty()) {
        return;
    }
    Utils::FormattedText formatText(text);
    // QList<FormattedText> ->
    auto parsedTexts = mAnsiCodeHandler.parseText(formatText);
    // this->insertPlainText(parsedTexts);
    auto cursor = textCursor();
    for (const auto parsedText : parsedTexts) {
        cursor.insertText(parsedText.text, parsedText.format);
    }
    // qDebug() << "SshConsole::GetExecStandardOutput:\n" << output;
}

void SshConsole::GetExecStandardError(const std::string& error) {
    this->setTextColor(mErrorColor);
    this->append(QString::fromStdString(error));
}

void SshConsole::DoWhenExecFinished() {
    this->setTextColor(mPromptColor);
    this->insertPlainText(mPrompt + mRelativePath);
    qDebug() << "DoWhenExecFinished";
    mIsExecStarted = false;
    isRemoveHead = false;
    mAnsiCodeHandler.Reset();
    this->setTextColor(mInfoColor);
    this->insertPlainText(" ");
    this->ensureCursorVisible();
}

// private

bool SshConsole::IsMouseInEditableZone()
{
    return true;
}

// pos is a global position
bool SshConsole::IsMouseInEditableZone(const QPoint &pos)
{
    const auto mouseCursor = this->cursorForPosition(pos);
    return mouseCursor.position() - textCursor().position() > mPrompt.length() + mRelativePath.length() + 1;
}

void SshConsole::ReplaceCurrentCommand(const QString& command)
{
    auto cursor = this->textCursor();
    cursor.movePosition(QTextCursor::StartOfLine);
    cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, mPrompt.length() + mRelativePath.length() + 1);
    cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
    cursor.insertText(command);
}

bool SshConsole::MoveCursorByKey(int key)
{
    if (key != Qt::Key_Left) {
        return false;
    }

    auto startCursor = this->textCursor();
    startCursor.movePosition(QTextCursor::StartOfLine);
    if (textCursor().position() - startCursor.position() > mPrompt.length() + mRelativePath.length() + 1) {
        auto cursor = this->textCursor();
        cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
        setTextCursor(cursor);
    }
    return true;
}

bool SshConsole::RemoveForwardByKey(int key)
{
    if (key != Qt::Key_Backspace) {
        return false;
    }
    auto startCursor = this->textCursor();
    startCursor.movePosition(QTextCursor::StartOfLine);
    if (textCursor().position() - startCursor.position() > mPrompt.length() + mRelativePath.length() + 1) {
        auto cursor = this->textCursor();
        cursor.deletePreviousChar();
        setTextCursor(cursor);
    }

    return true;
}

QString SshConsole::FilterUnusedChars(QString string)
{
    // qDebug() << "SshConsole::FilterUnusedChars:\n" << string;

    if (!isRemoveHead && mIsExecStarted) {
        const auto index = string.indexOf("\r");
        if (index == -1) {
            string.clear();
        } else {
            string.remove(0, index + 2);
        }
        isRemoveHead = true;
    }

    if (string.lastIndexOf(QRegularExpression(":.*?\\$")) == -1) {
        return string;
    }

    auto index = string.lastIndexOf(":");
    const auto textLength = string.length();
    mRelativePath = string.sliced(index + 1, textLength - index - 3);
    if (const auto indexN = string.lastIndexOf("\n"); indexN != -1) {
        string.remove(indexN + 2, textLength - indexN - 2);
        return string;
    }
    return "";
}
